package sip;

import data.DataHandler;
import model.SDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import model.SIP;
import rtp.RTPserver;

/**
 * Class that handle VoIP (SIP/SDP/RTP) Transactions
 */
public class VoIPserver extends Thread {

    private final static String CRLF = "\r\n";
    public static final int START_STAGE = 0;// Init stage
    public static final int INVITE_STAGE = 1;// Stage after client send invite
    public static final int RINGING_STAGE = 2;// Stage after server send ringing
    public static final int SDP_STAGE = 3;// Stage after server pick up the call  / send SIP contain SDP
    public static final int ACK_STAGE = 4;// Stage after client send ACK for server's SDP
    public static final int RTP_STAGE = 5;// Stage after server finish send RTP
    public static final int BYE_STAGE = 6;// Stage after server finish send RTP
    private DatagramSocket server;
    private DatagramPacket outpacket;
    private InetAddress obCaller;
    private int obIntCallerPort, obIntCallerStage;
    private DataHandler obDataHandler;
    private SDP obSDP;
    private SIP obSIP;
    private String obRequest, obStrSIPUser;
    private boolean isFinish;

    public VoIPserver(InetAddress obCaller, int obIntCallerPort,
            String obRequest, String obStrSIPUser, DatagramSocket server,
            DataHandler obDataHandler) {
        this.obCaller = obCaller;
        this.obIntCallerPort = obIntCallerPort;
        this.server = server;
        this.obRequest = obRequest;
        this.obDataHandler = obDataHandler;
        this.obStrSIPUser = obStrSIPUser;
        this.isFinish = false;
        this.obSIP = new SIP(
                obRequest,
                server.getLocalSocketAddress().toString().substring(1,
                server.getLocalSocketAddress().toString().indexOf(":")),
                obIntCallerPort);
    }

    public void run() {
        System.out.println("INFO: Packet received " + CRLF + obRequest);
        if (obSIP.getRequestType() != obSIP.IS_TRANSACTION_NOEXIST) {
            if (obSIP.isValidRequest()) {
                if (obSIP.isValidUser(obStrSIPUser)) {
                    obIntCallerStage = obDataHandler.getStage(obCaller.toString(),
                            obIntCallerPort);

                    /* Handle if Caller send SIP Invite Request */
                    if (obSIP.getRequestType() == obSIP.IS_INVITE
                            && obIntCallerStage == START_STAGE) {

                        // Update the stage and the SDP of this client
                        obDataHandler.updateStage(obCaller.toString(), obIntCallerPort, INVITE_STAGE, obSIP.getSDP());
                        obDataHandler.setSDP(obCaller.toString(), obIntCallerPort,
                                obSIP.getSDP());
                        // Wait for 1 seconds before send the ring, let the beep sound heard by client
                        try {
                            Thread.currentThread().sleep(1000);
                        } catch (InterruptedException ie) {
                            ie.printStackTrace();
                        }
                        obDataHandler.updateStage(obCaller.toString(),
                                obIntCallerPort, RINGING_STAGE);

                    } else if (obSIP.getRequestType() == obSIP.IS_ACK && obIntCallerStage == ACK_STAGE) {
                        /* Handle if Caller send SIP ACKS Request */
                        System.out.println("INFO: Sending Media RTP");
                        // Get the SDP of this client for this connection
                        this.obSDP = obDataHandler.getSDP(obCaller.toString(),
                                obIntCallerPort);
                        RTPserver rtpserver = new RTPserver(obCaller.getHostAddress(), obSDP.getRTPPort(),
                                obDataHandler.getVoiceName(), this);
                        rtpserver.send();

                        obDataHandler.updateStage(obCaller.toString(),
                                obIntCallerPort, RTP_STAGE);

                    } else if (obSIP.getRequestType() == obSIP.IS_BYE && obIntCallerStage != -1) {
                        /* Handle if Caller send SIP BYE Request */
                        boolean isSent = sendPacket(obSIP.getStatusLine(obSIP.BYE_OK)
                                + obSIP.getHeader(obSIP.BYE_OK) + CRLF);
                        // Remove Client from our table
                        obDataHandler.removeClient(obCaller.toString(),
                                obIntCallerPort);
                        stopService();

                    } else if (obSIP.getRequestType() == obSIP.IS_CANCEL && obIntCallerStage != -1) {
                        /* Handle if Caller send SIP Cancel Request */
                        // Remove Client from our table
                        obDataHandler.removeClient(obCaller.toString(), obIntCallerPort);
                        boolean isSent = sendPacket(obSIP.getStatusLine(obSIP.CANCEL_OK)
                                + obSIP.getHeader(obSIP.CANCEL_OK) + CRLF);
                        stopService();
                    }

                    obIntCallerStage = obDataHandler.getStage(obCaller.toString(), obIntCallerPort);
                    /*
                     * After sleeping for perform sleeping, check if the stage is
                     * still RINGING_STAGE or not. It means client doesnt send any
                     * CANCEL request
                     */
                    if (obIntCallerStage == RINGING_STAGE) {
                        // Send The Ringing Message
                        boolean isSent = sendPacket(obSIP.getStatusLine(obSIP.RINGING) + obSIP.getHeader(obSIP.RINGING) + CRLF);
                        // Get the SDP of this client for this connection
                        this.obSDP = obDataHandler.getSDP(obCaller.toString(), obIntCallerPort);

                        // Wait for 3 seconds before pick up the call, let the
                        // ringing sound heard
                        try {
                            Thread.currentThread().sleep(3000);
                        } catch (InterruptedException ie) {
                            ie.printStackTrace();
                        }
                        if (obSDP != null) {
                            // Update the stage of this client
                            obDataHandler.updateStage(obCaller.toString(), obIntCallerPort, SDP_STAGE);
                        }
                    }

                    obIntCallerStage = obDataHandler.getStage(obCaller.toString(), obIntCallerPort);
                    /*
                     * After sleeping for perform ringing, check if the stage is
                     * still SDP_STAGE or not. It means client doesnt send any
                     * CANCEL request
                     */
                    if (obIntCallerStage == SDP_STAGE) {
                        // Send the OK message to establish RTP conncetion
                        boolean isSent = sendPacket(obSIP.getStatusLine(obSIP.INVITE_OK)
                                + obSIP.getHeader(obSIP.INVITE_OK)
                                + CRLF
                                + obSDP.toString());
                        obDataHandler.updateStage(obCaller.toString(), obIntCallerPort, ACK_STAGE);

                    } else if (obIntCallerStage == BYE_STAGE && obSIP.getRequestType() == obSIP.IS_OK) {
                        /* after send bye, we might receive a 200 OK response, then
                         * try to remove Client from our table if it's still there */
                        obDataHandler.removeClient(obCaller.toString(), obIntCallerPort);
                        stopService();
                    }
                } else {
                    // The client try to call to invalid user
                    boolean isSent = sendPacket(obSIP.getStatusLine(obSIP.FAIL_USER)
                            + obSIP.getHeader(obSIP.FAIL_USER) + CRLF);
                    stopService();
                }
            } else {
                // The client try to request invalid request
                boolean isSent = sendPacket(obSIP.getStatusLine(obSIP.BAD_REQUEST)
                        + obSIP.getHeader(obSIP.BAD_REQUEST) + CRLF);
                stopService();
            }//End Valid
        }//End Transaction no exist
        obIntCallerStage = obDataHandler.getStage(obCaller.toString(), obIntCallerPort);
        while (obIntCallerStage == RTP_STAGE) {
            if (getIsFinish()) {
                //After send RTP, we can send BYE to client
                boolean isSent = sendPacket(obSIP.getStatusLine(obSIP.BYE)
                        + obSIP.getHeader(obSIP.BYE) + CRLF);
                obDataHandler.updateStage(obCaller.toString(), obIntCallerPort, BYE_STAGE);

                //Remove Client from our table, we dont wait for OK response, to make application robust.
                obDataHandler.removeClient(obCaller.toString(), obIntCallerPort);
                stopService();
            }
        }

    }// run end

    public void setIsFinish() {
        this.isFinish = true;
    }

    private boolean getIsFinish() {
        return isFinish;
    }

    private synchronized void stopService() {
        try {
            Thread.currentThread().stop();
        } catch (Exception ie) {
        }
    }

    /* Method to send UDP packets */
    private synchronized boolean sendPacket(String out) {
        System.out.println("INFO: Sending Packet " + CRLF + out);
        try {
            outpacket = new DatagramPacket(out.getBytes(), out.length(),
                    obCaller, obIntCallerPort);
            server.send(outpacket);
            return true;
        } catch (IOException e) {
            return true;
        }
    }
}// Class end

